I am using mobx and react hooks together . I have a useContext to get the store functions
const store = useContext(MyStore)
useEffect(() => {
if (init !== '') {
store.loading = true;
store.bulkApprove(init).then(data => {
store.unCheckAll();
});
}
}, [init]);
I can see bunch of warnings like below
React Hook useEffect has a missing dependency: 'store'. Either include it or remove the dependency array
I am really confused why do i need to include the store on dependency array
React Hook useEffect has a missing dependency: <dep>. Either include it or remove the dependency array simply means that the linter is warning you that you have a dependency that relies on an external value that may change.
store is subscribed to context MyStore. Meaning it is tracking its changes.
In case anyone is still looking I found a working solution on youtube
https://www.youtube.com/watch?v=0FS3pJa6rME
You basically wrap the store with a context and then wrap your function in an observer. This solution gets around the syntax hell I found myself in.
My store looks like this:
import { observable, decorate, action } from "mobx";
import { createContext } from "react";
class NewsFormStore {
formValues = [];
onChange = (name, value) => {
this.formValues[name] = value;
};
}
decorate(NewsFormStore, {
formValues: observable,
onChange: action
});
export const store = new NewsFormStore();
export const StoreContext = createContext(store);
Using the store for a simple textField can look like this:
import React, { useState, useContext } from "react";
import { observer } from "mobx-react-lite";
import { StoreContext } from "../../../../Stores/NewsFormStore";
export const OutlinedTextFields = observer(props => {
const store = useContext(StoreContext);
const classes = useStyles();
const [label] = useState(props.label !== "" ? props.label : "");
const [name] = useState(props.name !== "" ? props.name : "");
return (
<TextField
value={store.formValues[name]}
onChange={e => store.onChange(name, e.target.value)}
onBlur={e => store.onChange(name, e.target.value)}
label={label}
name={name}
/>
);
});
export default OutlinedTextFields;
Related
I'm using syncfusion react controls to add some functionality to my app. I want to call a method on the control in my functional component, but I haven't been able to get the ref set properly:
import React, {createRef, useEffect, useState} from "react";
import {AutoCompleteComponent} from "#syncfusion/ej2-react-dropdowns";
import "#syncfusion/ej2-base/styles/bootstrap.css";
import "#syncfusion/ej2-react-inputs/styles/bootstrap.css";
import "#syncfusion/ej2-react-dropdowns/styles/bootstrap.css";
const UserLookup = ({userSelected}) => {
const [searchString, setSearchString] = useState('');
const [items, setItems] = useState([]);
const helper = new QueryHelper();
let listObj = createRef();
const searchStringChanged = (args) => {
console.log(args.text);
if (args.text.length > 3) {
setSearchString(args.text);
}
}
const optionSelected = (event) => {
memberSelected(event.item.id);
}
useEffect(() => {
fetch('http://example.com/myendpoint')
.then(response.map((result) => {
listObj.current.showPopup(); // <-- this method should be called on the autocomplete component
return {
id: result.contactId,
label: result.firstName + ' ' + result.lastName
}
}))
.then(data => console.log(data));
}, [searchString]);
return (
<AutoCompleteComponent
id="user_search"
autofill={true}
dataSource={items}
fields={
{
value: 'label'
}
}
filtering={searchStringChanged}
select={optionSelected}
popupHeight="250px"
popupWidth="300px"
placeholder="Find a contact (optional)"
ref={listObj}
/>
);
};
export default UserLookup;
this always throws an error that Cannot read property 'showPopup' of null -- how do you set the ref for the instance of the AutoCompleteComponent so that you can call methods on it?
We can get the reference for the AutoComplete when it's rendered as a functional component with help of using useRef method instead of createRef method. Please find the modified sample from below.
Sample Link: https://codesandbox.io/s/throbbing-shadow-ddsmf
I am having URL as http://example.com/callback?code=abcd
I need to fetch value of code.
Below is my code:
import React from 'react';
import { useEffect } from 'react';
const Callback = () => {
useEffect(() => {
console.log("hiii");
}, []);
return (
<React.Fragment>
Callback
</React.Fragment>
);
};
export default Callback;
Please confirm how can I fetch that.
Thanks in advance.
Probably the best way is to use the useParams hook because you have a functional component. Read from the documentation as the following:
useParams returns an object of key/value pairs of URL parameters. Use it to access match.params of the current <Route>.
I would suggest the following:
import React from 'react'
import { useParams } from 'react-router-dom'
const Callback = () => {
let { code } = useParams()
console.log({ code });
return (
<React.Fragment>
Callback
</React.Fragment>
)
};
+1 from documentation:
You need to be using React >= 16.8 in order to use any of these hooks!
use this library 'query-string'
and use as
import queryString from 'query-string';
in constructor
constructor(props) {
const { location: { search } } = props;
const parsed = queryString.parse(search);
this.state = {
code: parsed.code,
}
}
hope it will solve your problem !
Try this
http://example.com/callback?code=abcd
// ReactJS
import React from "react";
import { useLocation } from "react-router-dom";
const MyComponent = () => {
const search = useLocation().search;
const code = new URLSearchParams(search).get("code");
console.log(code); //abcd
}
// VanillaJS
const code = window.location.search.split("=")[1];
console.log(code); //abcd
You can use the URLSearchParams interface to fetch the query string of a URL.
https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams
const queryParams = new URLSearchParams(window.location.search);
const code = queryParams.get("code");
First at App.js you should have react-router-dom
import { Route, Switch } from "react-router-dom";
Then inside switch you can define route parameters like in your case
<Route exact path="/callback/:code" component={YourComponentsName} />
At the components file you can get the parameters from the props
Functional Component
const code = props.match.params.code
Class Component
const code = this.props.match.params.code
Or next approach
const code = new URLSearchParams(this.props.location.search);
const code = query.get('code')
console.log(code)//abcd
You need to use a hook...
useEffect(() => {
let urlParams = new URLSearchParams(window.location.search);
console.log(urlParams.code || "empty");
}, []);
import React, { useEffect, useState } from 'react';
import { Card } from 'components/Card';
import { dateFilter } from 'helpers';
import Chart from 'chart.js';
import 'chartjs-chart-matrix';
import chroma from 'chroma-js';
import moment from 'moment';
const WeeklyTrafficCard = (props) => {
const { start, end, data, store } = props;
const capacity = store && store.capacity;
var numberOfweeks = 0; //representing how many weeks back
const dateArray = [];
var today = moment();
while (numberOfweeks < 10) {
var from_date = today.startOf('week').format('MM/DD/YY');
var to_date = today.endOf('week').format('MM/DD/YY');
var range = from_date.concat(' ','-',' ',to_date);
dateArray.push(range);
today = today.subtract(7, 'days');
numberOfweeks++;
//console.log(dateArray);
}
const [each_daterange, setDateRange] = useState();
I have this Component called WeeklyTrafficCard and I want to use the variable, each_daterange, in another component, which imported WeeklyTrafficCard as below to send the get request, clearly I cannot use each_daterange directly right here, how I can work around it?
import React, { useContext, useEffect, useState } from 'react';
import { WeeklyTrafficCard } from './WeeklyTrafficCard';
import { AppContext } from 'contexts/App';
import { API_URL } from 'constants/index.js';
import { todayOpen, todayClose } from 'helpers';
import moment from 'moment';
const WeeklyTrafficCardContainer = (props) => {
const { API } = useContext(AppContext);
const { store = {} } = props;
const [data, setData] = useState([]);
const open = todayOpen(store.hours, store.timezone);
const close = todayClose(store.hours, store.timezone);
useEffect(() => {
async function fetchData() {
const result = await API.get(`${API_URL}/api/aggregates`, {
params: {
each_daterange,
every: '1h',
hourStart: 13,
hourStop: 4
},
});
You should use a useEffect(prop drilling) to pass your variable in your parent:
import React, { Component } from "react";
import { render } from "react-dom";
import "./style.css";
const App = () => {
const [myVar, setMyVar] = React.useState('');
return (
<div>
<Child setMyVar={setMyVar} />
{myVar}
</div>
);
};
const Child = ({setMyVar}) => {
const myChildVar = "Hello world !"
React.useEffect( () => setMyVar(myChildVar),[]);
return <div> This is the child</div>
}
render(<App />, document.getElementById("root"));
Here is the repro on stackblitz
Understanding of the Problem
You want to pass data up to the parent from the child.
Manage each_daterange in the parent:
Instead of creating your useState variable each_daterange in the child you can declare it in the parent and pass down it's setter function. For instance:
const WeeklyTrafficCardContainer = (props) => {
const [eachDateRange, setEachDateRange] = useState();
return (
<div>
{/* your return */}
<WeeklyTrafficCard setEachDateRange={setEachDateRange} />
</div>
)
}
If you need to display eachDateRange in the traffic card, or the traffic card needs to completely own that variable, you can create another state variable in the parent and pass a callback to the child (essentially what is above but now you have two different state variables).
The parent becomes
const WeeklyTrafficCardContainer = (props) => {
const [requestDateRange, setRequestDateRange] = useState();
const updateRequestDateRange = (dateRange) => {
setRequestDateRange(dateRange)
}
return (
<div>
{/* your return */}
<WeeklyTrafficCard updateDateRange={updateRequestDateRange} />
</div>
)
}
Then in your WeeklyTrafficCard call props.updateDateRange and pass it the date range whenever each_daterange changes.
Ciao, of course you need a global state manager. My preferred is react-redux. In few word, react-redux allows you to have a state that is shared in all your components. Sharing each_daterange between WeeklyTrafficCardContainer and WeeklyTrafficCard will be very easy if you decide to use it.
This is the more appropriate guide to quick start with react-redux. have a nice coding :)
Keep the value outside of the component, where both can access it. There are other ways to do this, but just as a simple example you could create a simple "store" to hold it and reference that store from each component that needs it:
class Store {
setDateRange (newDateRange) {
this._dateRange = newDateRange;
}
get dateRange () {
return this._dateRange;
}
}
export default new Store(); // singleton; everyone gets the same instance
import store from './Store';
const WeeklyTrafficCard = (props) => {
// use current dateRange value
const dateRange = store.dateRange;
// set new dateRange
store.setDateRange( newDateRange );
// do other stuff
}
import store from './Store';
const WeeklyTrafficCardContainer = (props) => {
// use current dateRange value
const dateRange = store.dateRange;
// set new dateRange
store.setDateRange( newDateRange );
// do other stuff
}
If you want store updates to trigger component re-renders you'd need to add some higher order component plumbing, like redux's connect, or some other mechanism for triggering updates:
// pseudocode; make store an event emitter and return
// a component that re-renders on store events
store.connect = Component => {
return props => {
React.useEffect(() => {
store.addEventListener( ... )
return () => store.removeEventListener( ... )
})
}
}
Or if the components share a common parent, you could lift the state to the parent and pass the information to each component as props. If either component updates the value, the parent state change will trigger a re-render of both components with the new value:
const Parent = () => {
const [dateRange, setDateRange] = React.useState();
return (
<>
<WeeklyTrafficCardContainer
dateRange={dateRange}
onDateRangeChange={newRange => setDateRange(newRange)}
/>
<WeeklyTrafficCard
dateRange={dateRange}
onDateRangeChange={newRange => setDateRange(newRange)}
/>
</>
);
}
Let's rephrase the objective here.
Objective: access each_daterange from WeeklyTrafficCard component in WeeklyTrafficCardContainer component.
Note: simply put, choose the following case based on your problem.
choose using prop if the variable is to be accessed by only one component
choose using context if the variable is to be accessed by more than one components
Solution Cases:
Case A: using prop.
Case A.1. WeeklyTrafficCard is the parent of WeeklyTrafficCardContainer
each_datarange being passed from WeeklyTrafficCard component as prop to WeeklyTrafficCardContainer component
working example for reference: codesandbox - variable passed as prop
// WeeklyTrafficCard.jsx file
const WeeklyTrafficCard = () => {
const [each_daterange, setDateRange] = useState();
return (
<>
...
<WeeklyTrafficCardContainer eachDateRange={each_daterange} />
</>
);
};
// WeeklyTrafficCardContainer.jsx file
const WeeklyTrafficCardContainer = props => {
const eachDateRange = props.eachDateRange;
return (
<>
...
</>
);
};
Case A.2. WeeklyTrafficCard & WeeklyTrafficCardContainer are children of a parent, say WeeklyTraffic component
each_datarange will be present in WeeklyTraffic component which is shared among WeeklyTrafficCard component & WeeklyTrafficCardContainer component
// WeeklyTraffic.jsx file
const WeeklyTraffic = () => {
const [each_daterange, setDateRange] = useState();
return (
<>
...
<WeeklyTrafficCard eachDateRange={each_daterange} />
<WeeklyTrafficCardContainer eachDateRange={each_daterange} />
</>
);
};
// WeeklyTrafficCard.jsx file
const WeeklyTrafficCard = props => {
const eachDateRange = props.eachDateRange;
return (
<>
...
</>
);
};
// WeeklyTrafficCardContainer.jsx file
const WeeklyTrafficCardContainer = props => {
const eachDateRange = props.eachDateRange;
return (
<>
...
</>
);
};
Case B: using context.
follow blog example found: blog - react context
this is preferred way to implement if the variable/variables is/are shared or need to be accessed by more than 1 components
I need to write a test with the following steps:
get user data on mount
get project details if it has selectedProject and clientId when they change
get pages details if it has selectedProject, clientId, and selectedPages when they change
render Content inside Switch
if doesn't have clientId, Content should return null
if doesn't have selectedProject, Content should return Projects
if doesn't have selectedPages, Content should return Pages
else Content should render Artboard
And the component looks like this:
import React, { useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";
import { getUserData } from "../../firebase/user";
import { selectProject } from "../../actions/projects";
import { getItem } from "../../tools/localStorage";
import { getProjectDetails } from "../../firebase/projects";
import { selectPages } from "../../actions/pages";
import Pages from "../Pages";
import Projects from "../Projects";
import Artboard from "../Artboard";
import Switch from "../Transitions/Switch";
import { getUserId, getClientId } from "../../selectors/user";
import { getSelectedProject } from "../../selectors/projects";
import { getSelectedPages, getPagesWithDetails } from "../../selectors/pages";
import { getPagesDetails } from "../../firebase/pages";
const cachedProject = JSON.parse(getItem("selectedProject"));
const cachedPages = JSON.parse(getItem("selectedPages"));
const Dashboard = () => {
const dispatch = useDispatch();
const userId = useSelector(getUserId);
const clientId = useSelector(getClientId);
const selectedProject = useSelector(getSelectedProject) || cachedProject;
const selectedPages = useSelector(getSelectedPages) || cachedPages;
const pagesWithDetails = useSelector(getPagesWithDetails);
useEffect(() => {
dispatch(
getUserData(userId)
);
cachedProject && selectProject(cachedProject);
cachedPages && selectPages(cachedPages);
}, []);
useEffect(() => {
if (selectedProject && clientId) {
dispatch(
getProjectDetails(
clientId,
selectedProject
)
);
}
}, [selectedProject, clientId]);
useEffect(() => {
if (selectedPages && selectedProject && clientId) {
const pagesWithoutDetails = selectedPages.filter(pageId => (
!Object.keys(pagesWithDetails).includes(pageId)
));
dispatch(
getPagesDetails(
selectedProject,
pagesWithoutDetails
)
);
}
}, [selectedPages, selectedProject, clientId]);
const Content = () => {
if (!clientId) return null;
if (!selectedProject) {
return <Projects key="projects" />;
}
if (!selectedPages) {
return <Pages key="pages" />;
}
return <Artboard key="artboard" />;
};
console.log("Update Dashboard")
return (
<Switch>
{Content()}
</Switch>
);
};
Where I use some functions to fetch data from firebase, some to dispatch actions, and some conditionals.
I'm trying to get deep into testing with Jest and Enzyme. When I was searching for testing approaches, testing useEffect, variables, and conditions, I haven't found anything. All I saw is testing if a text changes, if a button has get clicked, etc. but what about testing components which aren't really changing anything in the DOM, just loading data, and depending on that data, renders a component?
What's the question here? What have you tried? To me it seems pretty straightforward to test:
Use Enzymes mount or shallow to render the component and assign that to a variable and wrap it in a store provider so it has access to a redux store.
Use jest.mock to mock things you don't want to actually want to happen (like the dispatching of actions) or use something like redux-mock-store.
Use that component ".find" to get the actual button you want.
Assert that, given a specific redux state, it renders correctly.
Assert that actions are dispatched with the proper type and payload at the proper times.
You may need to call component.update() to force it to rerender within the enzyme test.
Let me know if you have more specific issues.
Good luck!
Trying to get along with React new Hooks and ActionCable, but stuck with the problem that I can't get the right data in Rails when trying to send state.
I've tried to use send() method immediately after doing setState() and send my updated data, but for some reason, the data which received on the Rails part is old.
For example, if I put "Example" to the input I'll see "{"data"=>"Exampl"} on the Rails side. I suppose the data update the state later than my request goes.
If I send() value from e.target.value everything works fine
Therefore I've tried to use new useEffect() hook and send data there. But I get only data when rendering the page. Afterward, I don't get anything and sometimes get error RuntimeError - Unable to find subscription with an identifier. Seems like effect hook sends data too early or something.
I'm pretty new to Hooks and WebSockets. Would love to get any help here. I can share Rails code, but there is only a receiver and nothing else.
First exmaple:
import React, { useState, useEffect } from "react"
import ActionCable from 'actioncable'
function Component(props) {
const [data, setData] = useState("");
const cable = ActionCable.createConsumer('ws://localhost:3000/cable');
const sub = cable.subscriptions.create('DataChannel');
const handleChange = (e) => {
setData(e.target.value)
sub.send({ data });
}
return (
<input value={data} onChange={handleChange}/>
)
}
Tried to useEffect and move send() there:
useEffect(() => {
sub.send({ data });
}, [data]);
I'd love to find a way to correctly use React and ActionCable. And use hooks if it's possible.
I was trying an approach similar to Oleg's but I could not setChannel inside the action cable create subscription callback. I had to setChannel outside of the callback but within the useEffect hook. Below is the solution that worked for me.
create consumer in index.js and provide the consumer through Context to App.
index.js
import React, { createContext } from 'react'
import actionCable from 'actioncable'
... omitted other imports
const CableApp = {}
CableApp.cable = actionCable.createConsumer('ws://localhost:3000/cable')
export const ActionCableContext = createContext()
ReactDOM.render(
<Router>
... omitted other providers
<ActionCableContext.Provider value={CableApp.cable}>
<App />
</ActionCableContext.Provider>
</Router>,
document.getElementById('root')
)
Use the cable context in your child component and create subscription in useEffect hooks; unsubscribe in clean up
import React, { useState, useEffect, useContext } from 'react'
import { useParams } from 'react-router-dom'
... omitted code
const [channel, setChannel] = useState(null)
const { id } = useParams()
const cable = useContext(ActionCableContext)
useEffect(() => {
const channel = cable.subscriptions.create(
{
channel: 'MessagesChannel',
id: id,
},
{
received: (data) => {
receiveMessage(data)
},
}
)
setChannel(channel)
return () => {
channel.unsubscribe()
}
}, [id])
const sendMessage = (content) => {
channel.send(content)
}
You can register your cable at root component like that:
import actionCable from 'actioncable';
(function() {
window.CableApp || (window.CableApp = {});
CableApp.cable = actionCable.createConsumer('ws://localhost:3000/cable')
}).call(this);`
so it will be available as global variable;
and then in any component where you want to create channel and send data:
const [channel, setChannel] = useState(null);
useEffect(() => {
CableApp.cable.subscriptions.create(
{
channel: 'YourChannelName',
},
{
initialized() {
setChannel(this)
},
},
);
}, []);
return <button onClick={() => channel.send(some_data)} >Send counter</button>
Your problem is here:
const handleChange = (e) => {
setData(e.target.value)
sub.send({ data });
}
setData is like setState in that the state is only updated after the render i.e. after the function has exited. You are sending the current data not the new data. Try this:
const handleChange = (e) => {
const newData = e.target.value;
setData(newData)
sub.send({ data: newData });
}