简体   繁体   中英

Re-render Component after Redux state change not working?

I am building a small project using the react-flow-maker library. This library makes you able to create your own flow diagram with objects. A object can have it's own fields like textboxes, switches and dropdowns.

How does this library work?

The library has the following react component.

<FlowMaker
  logic={{
    introComponents: [],
    components: [],
  }}
  onChange={data => localStorage.setItem('flowMakerExample', JSON.stringify(data))}
  flow={JSON.parse(localStorage.getItem('flowMakerExample'))}
/>

Where the props used in this component have the following function:

  • logic -> Logic discribes the blocks and the inputs they have. It expects a object with the following properties for example.

     let logic = { introComponents: [ 'hello-world' ] components: [ { name: 'hello-world' title: 'Hello World' inputs: [ { name: 'options', title: 'Options', type: 'dropdown', default: 'c', options: [ {title: 'A', value: 'a'}, {title: 'B', value: 'b'}, {title: 'C', value: 'c'}, {title: 'D', value: 'd'}, {title: 'E', value: 'e'}, ] } ], next: 'hello-world' } ] }
    • onChange -> This returns a the flow data from when a user changes something
    • flow -> Here you can add a flow to show when the drawing gets mounted, handy if you remove the component from the screen or when the drawing needs to be persistent.

    My goal:

    1. Create a block with a dropdown, fetch by API a list of items and put them in the dropdown as title and value
    2. If the user changes something in the diagram, do a new fetch and update the options of the dropdown.

    I've implemented a GET request that returns the following JSON list:

     [ {"name":"name_0","sid":"0"}, {"name":"name_1","sid":"1"}, {"name":"name_2","sid":"2"}, {"name":"name_3","sid":"3"} ]

Logic.js this file contains the logic used in the FlowMaker component. Here I map the applications to right format for the options used in the dorpdown.

    const Logic = async (applications, ..., ...) => {
      return {
        introComponents: [
          'hello-world'
        ],
    
        components: [
          {
            name: 'hello-world',
            title: 'hello world',
            tooltip: 'This is hello',
            inputs: [
              ...
              {
                name: 'applicationName',
                title: 'Application name',
                type: 'dropdown',
                options: [
                  ...applications.map(app => (
                    {title: app.name, value: app.name + ' - ' + app.sid})
                  )
                ]
              },
              ...
            ],
            next: 'hello-world'
          },
          ...
        ]
      }
    }
    export default Logic;
    

drawerReducer.js my reducer where I initailize the new state for this drawer.

const initialState = {
    logic: null,
    data: null,
    applications: [],
    ...
}

const drawerReducer = (state = initialState, action) => {
    switch(action.type) {
        case LOGIC:
            return {
                ...state, 
                logic: action.payload
            }
        case DATA:
            return {
                ...state,
                data: action.payload
            }
        case APPLICATIONS: 
            return {
                ...state,
                applications: action.payload
            }
        ...
        default:
            return state;
    }
}

export default drawerReducer;

drawerAction.js contains my actions where fetch the new applications, set the new data and logic.

...
import Logic from '../utils/Logic'
import { LOGIC, APPLICATIONS, ..., ..., DATA } from './types'

export const setLogic = (applications, ..., ...) => dispatch => {
    Logic(applications, ..., ...)
    .then(newLogic => dispatch({
        type: LOGIC,
        payload: newLogic
    }))
}

export const setData = (newData) => dispatch => {
    dispatch({
        type: DATA,
        payload: newData
    })
}

export const setApplications = () => dispatch => {
    ApplicationList()
    .then(newApplications => dispatch({
        type: APPLICATIONS,
        payload: newApplications
    }))
} 

...

drawing.js here I've put the FlowMaker component and get everything together. You can see that I am using a useEffect hook to update the applications and then update the logic when the data prop changes.

import React, {useEffect} from 'react'
import FlowMaker from 'flowmaker'
import '../styles/flowmaker.css'
import Loader from '../utils/Loader'
import {setApplications, setData, setLogic } from '../actions/drawerAction'
import { connect } from 'react-redux'

const Drawer = ({logic, data, applications, doSetLogic, doSetData, doSetApplications}) => {

    useEffect(() => {
        doSetApplications() //dispatch new applications
        doSetLogic(applications) //dispatch to set the new logic with the newly fetched applications
        return () => {
            //cleanup
        }
    }, [data])

    return (
        <div className='drawer-canvas'>
                { logic ? 
                <>
                    <ButtonGroup />
                    <FlowMaker
                    logic={logic} //intial state of the diagramoptions
                    onChange={newData => doSetData(newData)}
                    flow={data}
                    />
                </>
                : <Loader />
                }
            </div>
    )
}

const mapStateToProps = state => ({
    logic: state.drawer.logic,
    data: state.drawer.data,
    applications: state.drawer.applications,
    ...
})

const mapDispatchToProps = {
    doSetLogic: setLogic,
    doSetData: setData,
    doSetApplications: setApplications,
    ...
}

export default connect(mapStateToProps, mapDispatchToProps)(Drawer) 

My problem My problem is that when the useEffect data depenceny is hit. The diagram is not re-rendering the new applications options in my diagram as the new options while the logic state in Redux did change.

This is my logic state before a do a data onchange action. You can see that the options are a empty list.

在此处输入图像描述

Now I've added a new block in my diagram. That means that the data action will fire and the newData will be set as data, next the useEffect is triggered due the depenency [data] and the logic is set with the new logic, which means that applicationName dropdown must be filled with new options and that is true.

添加新对象(更改的数据)后我的 redux 状态逻辑

Now with a new redux logic action done I expect that the options are there, but they are not and that is weird because in the second picture you can see that the logic DOES update. 该图显示了空选项,而逻辑状态有选项

To conclude; my question is how can I re-render this component with the new set Redux state? I thougth when you are changing the redux state a re-render is automatily triggered like setState. Any thougths on this problem?

I know this is a lot of text / code / picture and sorry for that, i've just didnt had any better idea how to do it otherwise.

Since this week there is a new update of the package that I was using. This update makes it possible to re-render component items on specific data changes using a getInputs function. In the example main.js file there is a example logic on this.

{
      name: 'frontend',
      tooltip: 'Information about the proxy/load balancing server',
      title: 'Frontend',
      getInputs(info) {
        const isHttpsInputs = info.inputs.https ? [
          {
            name: 'sslCert',
            title: 'Add ssl cert',
            tooltip: 'Add a ssl certificate',
            type: 'switch',
            default: true,
          }
        ] : [];
        return [
          {
            name: 'server',
            title: 'Server',
            type: 'text',
            tooltip: 'The address of the proxy/load balancing server',
            validation: domainCheck,
          }, {
            name: 'https',
            title: 'The server traffic is https',
            type: 'switch',
            default: true,
          },
          ...isHttpsInputs,
          {
            name: 'port',
            title: 'Web server port',
            type: 'number',
            default: 443,
            validation: portCheck,
          }
        ]
      },
      next: 'backend'
    },

The getInputs will check if the info.inputs.https is checked, if true then a extra field will be added (or updated) based on that.

In your useEffect, you seem to be dispatching the action which wants to use the updated state, however state updated are not immediate and are affected by closures.

You would need to split that logic into another useEffect

useEffect(() => {
    doSetApplications() //dispatch new applications
}, [data]);

useEffect(() => {
  if(applications) {
    doSetLogic(applications) //dispatch to set the new logic with the newly fetched applications
  }
}, [applications]);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM