1

I have been trying to connect the store to my existing toolkit the useStoreState acts as a useSelector I am not sure if it is ideal for my use case. I am wondering if there is a way to either connect the store directly to my existing redux toolkit store or whether I would have to always dispatch it to the store.

My current issue is that I am trying to place the active elements into my store and have each object linked another item, however since useStoreState returns the entire object this in turn becomes non-serializable when I dispatch it into the store. This means that whenever an element is deleted/added it refreshes that stored state in the redux toolkit store, which in turn updates the store with the new object but all linked objects lose their connection to that element and get deleted as well.

example of what is in my state:

[{elementID: 1, linkedText: "bla bla"}, {elementID: 2, linkedText: "bla bla for 2"}, {elementID: 3}]

the moment I add/remove an element to it, lets say element 3, this resets the state to

[{elementID: 1}, {elementID: 2}]

Which removes all the stored linked text?

Is there any possible work around here that I could look at? Perhaps being able to store the results directly into the existing redux toolkit store somehow? I know easy peasy is used, and I tried researching on how to connect it to the store which I failed in achieving too.

export default connect((state) => ({ elements: state}))(EditorReactFlow);

Also am I supposed to see the internal store of react-flow-renderer in the redux devtools? I can only see my own app's one, so I presume not?

Edit:

Currently what I dispatch are Elements from the actual react-flow-renderer, below I have posted the component. On useEffect the elements are dispatched to addStep() in my redux slice which deconstructs it to get the ID and stores that in the store.

function EditorReactFlow() {
  const classes = useStyles();
  const reactFlowWrapper = useRef(null);
  const [reactFlowInstance, setReactFlowInstance] = useState(null);
  const [elements, setElements] = useState(initialElements);
  const [name, setName] = useState("")
  const dispatch = useDispatch();

  useEffect(() =>{
    dispatch(addStep(elements))
  },[dispatch, elements])

  const onConnect = (params) => setElements((els) => addEdge(params, els));
  const onElementsRemove = (elementsToRemove) =>
    setElements((els) => removeElements(elementsToRemove, els));

  const onLoad = (_reactFlowInstance) =>{
    _reactFlowInstance.fitView();
    setReactFlowInstance(_reactFlowInstance);}

  const addNode = () => {
      setElements(e => e.concat({
          id: getId(),
          data: {label: `${name}`},
          position: {x: 0, y: -5}, 
      },
      ))
  };

  return (
    <Container maxWidth="lg">
      <CssBaseline />
      <div className={classes.paper}> </div>
      <div className="{classes.heroContent} reactflow-wrapper" ref={reactFlowWrapper}>
          <ReactFlow 
            elements={elements} 
            onElementsRemove={onElementsRemove}
            style={{width:'100%',height: '90vh'}}
            onLoad={onLoad}
            onConnect = {onConnect}
            >            
            <Controls /> 
          </ReactFlow>
        <input type="text"
          onChange={e => setName(e.target.value)}
          name="title"/>
          <button 
          type="button"
          onClick={addNode}
          >Add Node</button>
      </div>
      <RfEditorSidebar />
    </Container>
  );
}

export default connect(
  (state) => ({ todos: state})
)(EditorReactFlow);

I use a WYSIWYG editor to store data to a particular element. AddStep creates the elementID and then editStep stores the WYSIWYG data onto that specific ID. Below are both the addStep and editStep reducer in my slice:

addStep(state, action) {
  const allSteps = action.payload;
  return {
    ...state,
    data: allSteps.map((element) => {
      return { element: element.id };
    }),
  };
},
editStep(state: any, action) {
  return {
    ...state,
    data: state.data.map((step) =>
      step.element === action.payload.element
        ? { ...step, step: action.payload.step }
        : step
    ),
  };
},
Asif333
  • 47
  • 1
  • 6
  • Hi there, could you please share the code for the action you dispatched? Without additional information it's hard to tell why the state is overwritten like this. – Anon7250 Mar 18 '21 at 01:55
  • Hi thanks for the response, I have updated the code to show the complete component and all the reducers. – Asif333 Mar 18 '21 at 02:54

1 Answers1

3

EditorReactFlow stores "elements" as both an internal state and redux's global state, which is problematic.

  • Elements are stored as internal states of the EditorReactFlow component: const [elements, setElements] = useState(initialElements);
  • Elements are also used in dispatch calls to update Redux's global state: dispatch(addStep(elements))
  • This is problematic because both types of states will try to update a view / react component in their own ways, and also because in general, it's bad to have two different sources of truth for you data.

Usually, if a state needs to be stored in Redux, then it's retrieved and updated through Redux, without using useState, which isn't part of Redux.

Instead of useState, please consider using connect to map Redux states to props, by doing the following:

  • Remove const [elements, setElements] = useState(initialElements);

  • Change function signature from function EditorReactFlow() { to function EditorReactFlow(props) {

  • Instead of using elements variable to read elements, use props.todos.data

  • Remove this invocation of useEffect (See next item for explanation)

    useEffect(() =>{
      dispatch(addStep(elements))
    },[dispatch, elements])
    
  • Instead of using setElements function to update elements list, directly call

    dispatch(addStep(...)) /* can be repeated for other actions*/

    as part of any callback function, such as addNode.


EDIT:

In the end, your EditorReactFlow should be something like this:

function EditorReactFlow(props) {
  const classes = useStyles();
  const reactFlowWrapper = useRef(null);
  const [reactFlowInstance, setReactFlowInstance] = useState(null);
  const [name, setName] = useState("")
  const dispatch = useDispatch();

  let elements = props.todos.data;  // Redux passes elements list through props

  const onConnect = (params) => { 
    // Here, addEdge should be a Redux action, and needs to be dispatched
    dispatch(addEdge(params, elements));
  };

  const onElementsRemove = (elementsToRemove) => {
    // Here, removeElementsshould be a Redux action, and needs to be dispatched
    dispatch(removeElements(elementsToRemove, elements));
  };

  const onLoad = (_reactFlowInstance) =>{
    _reactFlowInstance.fitView();
    setReactFlowInstance(_reactFlowInstance);}

  const addNode = () => {
      dispatch(addStep(elements.concat({
          id: getId(),
          data: {label: `${name}`},
          position: {x: 0, y: -5}, 
      }))
  };

  return (
    <Container maxWidth="lg">
      <CssBaseline />
      <div className={classes.paper}> </div>
      <div className="{classes.heroContent} reactflow-wrapper" ref={reactFlowWrapper}>
          <ReactFlow 
            elements={elements} 
            onElementsRemove={onElementsRemove}
            style={{width:'100%',height: '90vh'}}
            onLoad={onLoad}
            onConnect = {onConnect}
            >            
            <Controls /> 
          </ReactFlow>
        <input type="text"
          onChange={e => setName(e.target.value)}
          name="title"/>
          <button 
          type="button"
          onClick={addNode}
          >Add Node</button>
      </div>
      <RfEditorSidebar />
    </Container>
  );
}

export default connect(
  // Ask Redux to pass Redux state to "props.todos"
  // According to your "addStep" reducer, elements are stored as "state.data"
  // So, elements list will be passed as "props.todo.data"
  (state) => ({todos: state})  
)(EditorReactFlow);

This might point you to the right direction. Please let me know if this helps.

Anon7250
  • 148
  • 6
  • Yes it all makes perfect sense thank you, I think I can definitely continue forward now thank you. I'll have to apply it to most handlers so that they update my own global redux store and use that essentially. – Asif333 Mar 18 '21 at 13:33